package edu.ccut.saturn.component.impl.node;

import java.util.List;

import edu.ccut.saturn.component.ComponentManager;
import edu.ccut.saturn.component.DefaultComponent;
import edu.ccut.saturn.component.IComponent;
import edu.ccut.saturn.component.ILogicContext;
import edu.ccut.saturn.component.IParameterContext;
import edu.ccut.saturn.component.IParameterInfo;
import edu.ccut.saturn.component.SaturnComponentException;
import edu.ccut.saturn.component.SaturnValidateFailedException;
import edu.ccut.saturn.component.impl.SaturnLogicComponent;
import edu.ccut.saturn.component.loader.node.BaseComponentNodeInfoLoader;
import edu.ccut.saturn.component.loader.node.NodeInfoLoader;
import edu.ccut.saturn.component.utils.ExceptionUtils;
import edu.ccut.saturn.component.xml.ISaturnElement;
import edu.ccut.saturn.component.xml.SaturnElement;

/**
 * component结点位于logic组件内，被logic组件调用。
 * 每个component结点代表对一个component组件的引用。component结点负责获得用户配置的执行参数，并激活引用的component组件的执行方法。
 * <p>
 * <code>BaseComponentNodeInfo</code>是component结点的实现类，同时也是逻辑结点<code>LogicComponentNodeInfo</code>的父类。
 * 
 * <li>记录了<code>exceptionId</code>信息,表示在结点执行过程中出现异常时转到哪个结点处理。
 * <li>记录了<code>key</code>信息，用于记录被结点调用的组件的键值。
 * 
 * @author 谷钰
 * @author 温建
 * 
 * @version 1.0 2008.8.15
 * 
 * @see AbstractNodeInfo
 * @see ILogicContext
 * @see IParameterContext
 * 
 */
public class BaseComponentNodeInfo extends AbstractNodeInfo {

	private String exceptionId;

	private String key = null;

	private static final String EXCEPTION_KEY = "__exception_key__";

	public BaseComponentNodeInfo(String id, String name, String description,
			String nextId, List<IParameterInfo> parameters, String key,
			String exceptionId, String x, String y) {

		super(id, name, description, nextId, parameters, x, y, "component");
		this.key = key;
		this.exceptionId = exceptionId;
	}

	/**
	 * 根据组件键值获得组件实例
	 * 
	 * @return 组件实例
	 * @throws SaturnComponentException
	 */
	public IComponent getComponent() throws SaturnComponentException {
		return ComponentManager.getInstance().getComponent(key);
	}

	/**
	 * 获得引用的组件的键值
	 * 
	 * @return 组件键值
	 */
	public String getKey() {
		return key;
	}

	/**
	 * 根据结点运行时的上下文信息生成被调用组件的参数上下问对象
	 * 
	 * @param logicContext
	 *            逻辑上下文对象，记录结点运行时的上下文信息
	 * @return IParameterContext 参数上下文对象，记录组件的输入输出参数
	 * @throws SaturnComponentException
	 * @throws SaturnValidateFailedException
	 */
	protected IParameterContext createParameterContext(
			ILogicContext logicContext) throws Exception {

		return logicContext.createParameterContext(this.getParameterInfos());
	}

	/**
	 * logic结点和component结点的执行逻辑。
	 * 首先获得待执行组件实例；然后根据逻辑上下文logicContext生成参数上下文；然后以参数上下文为参数，调用组件的执行方法；最后根据组件的执行结果更新逻辑上下文。
	 * 当组件执行过程出现异常时，如果没有指定异常结点则向外层抛出这个异常；如果指定了异常处理结点，则要考虑此结点是否在数据库事务范围之内的情况。
	 * 如果这个结点在数据库事务范围之内，并且不是嵌套的事务的内部结点，则转到异常结点处理，否则继续抛出异常。
	 * 在发生异常，并决定转到异常处理结点进行处理后，在logicContext中添加了以”__exception_key__“为键值的变量，用这个变量保存异常对象。
	 * 流程中的其它结点可以从上下文中获得这个异常对象。
	 * <p>
	 * 为了保证这个方法是线程安全的，所以对运行时的状态的保存应该放到参数逻辑上下文中<code>logicContext</code>，
	 * 如异常处理结点作为当前结点的下一结点时，不能直接修改当前结点的属性nextId，而应该把下一结点信息保存到上下文中。
	 * 
	 * @param logicContext
	 *            结点在运行时的上下文信息
	 * 
	 * @throws Exception
	 */
	public void doExecute(ILogicContext logicContext) throws Exception {
		IComponent component;
		try {
			component = this.getComponent();
		} catch (SaturnComponentException e) {
			logger.error("ERROR : { message : '" + ExceptionUtils.getExMessage(e) + "', process: '" + logicContext.getId() + "', node : '" + 
					this.getNodeType() + " " + this.getId() + " " + this.getName() + "'}");
			throw e;
		}
		
		List<IParameterInfo> parameterInfos = this.getParameterInfos();
		IParameterContext parameterContext = null;

		try {
			parameterContext = createParameterContext(logicContext);
			ComponentManager.beforeExecuteComponent(this, parameterContext);
			
			if(component instanceof DefaultComponent) {
				DefaultComponent baseComponent = (DefaultComponent)component;
				baseComponent.doExecute(parameterContext);
			} else {
				SaturnLogicComponent logicComponent = (SaturnLogicComponent)component;
				logicComponent.execute(parameterContext);
			}
			
		} catch (Exception e) {
			logger.error("ERROR : { message : '"+ ExceptionUtils.getExMessage(e)+"', process: '"+ logicContext.getId()+"', node : '" 
					+ super.getNodeType() + " " + super.getId() + " " + super.getName() + "'}");

			if (this.exceptionId != null && this.exceptionId.length() > 0) {
				logicContext.setNodeRouter(this.getId(), this.exceptionId);
				logicContext.setValue(EXCEPTION_KEY, e);
				logger.debug("the next node to excute after the node ["
						+ this.getId() + "] change to node ["
						+ this.exceptionId + "] to handler the exception!");

				// TODO:supports常量应该写在哪
				// TODO:关于事务的信息没有放到一个地方集中处理，程序中到处都有
				// TODO:什么时候抛出异常
				if ("supports".equals(parameterContext.getTransactionType())) {

					if ("supports".equals(logicContext.getTransactionType())) {
						throw e;
					}
				}

			} else {
				throw e;
			}

		} finally {
			this.refreshContextFromParameterContext(logicContext,
					parameterInfos, parameterContext);

			ComponentManager.afterExecuteComponent(this, parameterContext);
			
			//防止内存泄露
			if(parameterContext!=null){
				parameterContext.setLogicContext(null);
			}
		}
	}

	/**
	 * 获得异常节点id
	 * 
	 * @return 异常节点id
	 */
	public String getExceptionId() {
		return exceptionId;
	}

	private void refreshContextFromParameterContext(ILogicContext logicContext,
			List<IParameterInfo> parameterInfos, IParameterContext context) {

		logicContext
				.refreshContextFromParameterContext(parameterInfos, context);
	}

	public ISaturnElement toSaturnElement() {
		ISaturnElement baseComponentElement = new SaturnElement(
				BaseComponentNodeInfoLoader.KEY_NODETYPE_COMPONENT);

		baseComponentElement.addAttribute(NodeInfoLoader.KEY_NODE_EXCEPTION,
				this.exceptionId);

		baseComponentElement.addAttribute(NodeInfoLoader.KEY_NODE_ID, this
				.getId());

		baseComponentElement.addAttribute(NodeInfoLoader.KEY_NODE_X, this
				.getX());

		baseComponentElement.addAttribute(NodeInfoLoader.KEY_NODE_Y, this
				.getY());

		baseComponentElement.addAttribute(NodeInfoLoader.KEY_NODE_NEXT, this
				.getNextId());

		ISaturnElement keyElement = new SaturnElement(NodeInfoLoader.KEY_NODE_KEY,
				this.key);

		ISaturnElement nameElement = new SaturnElement(
				NodeInfoLoader.KEY_NODE_NAME, this.getName());

		ISaturnElement descriptionElement = new SaturnElement(
				NodeInfoLoader.KEY_NODE_DESCRIPTION, this.getDescription());

		ISaturnElement parametersElement = new SaturnElement(
				NodeInfoLoader.KEY_PARAMETER_LIST);

		if (this.getParameterInfos() != null) {

			for (IParameterInfo parameterInfo : this.getParameterInfos()) {
				parametersElement.addSubElement(parameterInfo.toSaturnElement());
			}
		}
		baseComponentElement.addSubElement(keyElement);
		baseComponentElement.addSubElement(nameElement);
		baseComponentElement.addSubElement(descriptionElement);
		baseComponentElement.addSubElement(parametersElement);
		return baseComponentElement;
	}

}
